#include "math_parser.h"
#include "converter.h"
#include "exceptions.h"
#include "str.h"
#include "global_variables.h"

#define MAX_FACT 150
#define MAX_NUMBER 1e64

#define MAX_INPUT_LENGTH 256

#define NUMBER_EXCEPTION "Reading number >>> failed!"
#define INPUT_CHECK_FAILED "Checking input >>> failed!"
#define INPUT_TOO_LONG "Checking input >>> too long!"
#define PARENTHESES_CHECK_FAILED "Checking parentheses >>> failed!"
#define UNDEFINED_CHARACTERS "Undefined character(s)!"
#define OUT_OF_RANGE "Out of range!"
#define INVALID_OPERATION "Invalid operation(s)!"

////

static double fact(unsigned int k) {
  // RECURSIVE
  if (k == 0 || k == 1)
    return 1.0;
  else
    return k * fact(k - 1);
} // fact

// writes into the result; 2 as long as the input could be divided by 2
// and finally the part that cannot be divided by 2 anymore
static unsigned long long check_even(unsigned long long input, string &result) {

  while (input != 0 && input % 2 == 0) {
    result += "2 ";
    input /= 2;
  } /* while */

  return input;
} // check_even

//  i is a counter starting by 3. it counts like 3, 5, 7, ...
//  it is used to avoid the use of global variables for counting
static void check_odd(unsigned long long input, unsigned int i, string &result) {
  unsigned long long input_root = (unsigned long long) sqrt((double) input);
  string str_cnv;

  while (i <= input) {
    if (input % i == 0) {
      do {
        result += converter::to_string(i, str_cnv);
        result += ' ';
        input /= i;
      } while (input % i == 0);

      check_odd(input, i + 2, result); // recursive call

      return;
    } else {

      if (i > input_root) {
        result += converter::to_string(input, str_cnv);
        return;
      } else
        i += 2; // counts like 3, 5, 7, 9, ...

    } // else
  } // while
} // check_odd

static string get_factors(unsigned long long input) {
  string result;
  input = check_even(input, result);
  check_odd(input, 3, result);

  return result;
} // get_factors

static double solve(const string &expression) {
// expression must be in the following form "a:b;c:x" or "a:b;x:c"
  const size_t i = expression.find(';');

  if (i == string::npos || i >= expression.length())
    return 0.0;

  int a, b, c;
  a = b = c = 0;

  double result = 0.0;

  if (expression[i + 1] == 'x') {
    sscanf(expression.c_str(), "{%d:%d;x:%d}", &a, &b, &c);
    result = (double) (a) / b * c;
  } else {
    sscanf(expression.c_str(), "{%d:%d;%d:x}", &a, &b, &c);
    result = (double) (b) / a * c;
  } // else

  return result;
} // solve

////

static double read_next_number(const string &s, size_t &position) {
  double result = 0.0;
  const size_t start_position = position;
  const size_t len = s.length();
  static const string allowed_symbols = "+-.e";

  if (position >= len)
    throw parameter_exception(NUMBER_EXCEPTION);
  else if (isdigit((unsigned char) s[position]) == false && sign_symbols.find(s[position]) == string::npos && s[position] != '.') // a number can only begin with [0-9] or +-.
    throw parameter_exception(NUMBER_EXCEPTION);

  if (position < len && sign_symbols.find(s[position]) != string::npos)
    ++position;

  if (position >= len)
    throw parameter_exception(NUMBER_EXCEPTION); // for example "2+-"

  while (position < len && (isdigit((unsigned char) s[position]) || allowed_symbols.find(s[position]) != string::npos)) {
    if (sign_symbols.find(s[position]) != string::npos)
      if (s[position - 1] != 'e') // sign symbols are only allowed at position of zero or after e
        break;

    ++position;
  } // while

  if (s[position - 1] == 'e')
    throw parameter_exception("A number cannot end with 'e'!");

  string tmp(s, start_position, position - start_position);
  istringstream stream(tmp);
  stream >> result;

  --position;
  return result; // position: inclusive
} // read_next_number

static double read_previous_number(const string &s, size_t &position) {
  // spaces must be removed before
  static const string allowed_symbols = "+-.e";

  if (position == string::npos)
    throw parameter_exception(NUMBER_EXCEPTION);

  while (position > 0 && (isdigit((unsigned char) s[position]) || allowed_symbols.find(s[position]) != string::npos)) {
    if (sign_symbols.find(s[position]) != string::npos)
      if (isdigit((unsigned char) s[position - 1]) || s[position - 1] == '.') {
        // sign symbols are only allowed at position of zero or after e
        ++position;
        break;
      } // if

    --position;
  } // while

  if (position > 0 && isdigit((unsigned char) s[position]) == false && sign_symbols.find(s[position]) == string::npos)
    // if not beginning with a digit or sign
    ++position; // example: input: "-1234567890.12345*-1234567890.12345" position = (length of input)

  size_t pos1 = position;
  return read_next_number(s, pos1); // position: inclusive
} // read_previous_number

////

static double LOG_F(const string &s, size_t &position) {
  position = position + strlen("LOG");
  double result = read_next_number(s, position);

  if (result > 0)
    result = log10(result);
  else
    throw parameter_exception("Log of numbers smaller than or equl to zero is undefined!");

  return result;
}

static double LN_F(const string &s, size_t &position) {
  position = position + strlen("LN");
  double result = read_next_number(s, position);

  if (result > 0)
    result = log(result);
  else
    throw parameter_exception("Ln of numbers smaller than or equl to zero is undefined!");

  return result;
}

static double SIN_F(const string &s, size_t &position) {
  position = position + strlen("SIN");
  return sin(read_next_number(s, position) * PI / 180);
}

static double COS_F(const string &s, size_t &position) {
  position = position + strlen("COS");
  return cos(read_next_number(s, position) * PI / 180);
}

static double TAN_F(const string &s, size_t &position) {
  position = position + strlen("TAN");
  return tan(read_next_number(s, position) * PI / 180);
}

static double INT_F(const string &s, size_t &position) {
  position = position + strlen("INT");
  return floor(read_next_number(s, position));
}

static double ABS_F(const string &s, size_t &position) {
  position = position + strlen("ABS");
  double result = read_next_number(s, position);

  if (result < 0)
    result = -result;

  return result;
}

static double SGN_F(const string &s, size_t &position) {
  position = position + strlen("SGN");
  double result = read_next_number(s, position);

  if (result > 0)
    result = 1;
  else if (result < 0)
    result = -1;

  return result;
}

static double SQR_F(const string &s, size_t &position) {
  position = position + strlen("SQR");
  double result = read_next_number(s, position);

  if (result < 0)
    throw parameter_exception("Sqr of numbers smaller than zero is undefined!");
  else
    result = sqrt(result);

  return result;
}

static string FAC_F(const string &s, size_t &position) {
  position = position + strlen("FAC");
  const double d = read_next_number(s, position);

  if (d >= MAX_NUMBER)
    throw parameter_exception("The result of factor is out of range!");
  else if (floor(d) != d)
    throw parameter_exception("Factor of double numbers is udefined!");

  return get_factors((unsigned long long) d);
}

static double B2D_F(const string &s, size_t &position) {
  position = position + strlen("B2D");

  size_t k = position;
  while (k < s.length() && (s[k] == '0' || s[k] == '1'))
    ++k;

  const double result = strtoul(s.substr(position, k - position).c_str(), 0, 2);

  position = k - 1;

  return result;
}

static double H2D_F(const string &s, size_t &position) {
  position = position + strlen("H2D");

  size_t k = position;
  while (k < s.length() && IS_HEX(s[k]) == true)
    ++k;

  const double result = strtoul(s.substr(position, k - position).c_str(), 0, 16);

  position = k - 1;

  return result;
}

static double SOLVE_F(const string &s, size_t &position) {
  // solve{a:b;c:x}
  position = position + strlen("SOLVE");
  size_t k = position;
  double result = 0.0;

  if (s[k] != '{')
    throw parameter_exception(NUMBER_EXCEPTION);

  while (k < s.length() && s[k] != '}')
    ++k;

  if (k < s.length()) {
    result = solve(s.substr(position, k - position + 1));
    position = k;
  } else
    throw parameter_exception(NUMBER_EXCEPTION);

  return result;
}

////

string math_parser::par_parse(const string &s, size_t &begin, size_t &end) {
  begin = s.find('(');

  if (begin == string::npos) {
    end = string::npos;
    return global_empty_string;
  } // if

  size_t i = begin + 1;

  while (i < s.length() && s[i] != ')') {
    if (s[i] == '(')
      begin = i;

    ++i;
  } // while

  end = i;
  return s.substr(begin + 1, end - begin - 1);
} // par_parse

void math_parser::fun_parse(string &s) {
  double d = 0.0;
  size_t position_of_fn, i = 0, j = 0;

  static const string math_function[] = {
  // 0      1     2      3      4      5      6      7       8      9      10     11     12
    "log", "ln", "sin", "cos", "tan", "int", "abs", "sgn",  "sqr", "fac", "b2d", "h2d", "solve"
  };

  const unsigned short nr_of_fn = sizeof(math_function) / sizeof(string);
  string str_cnv;

  while (i < nr_of_fn) {
    j = s.find(math_function[i]);

    if (j != string::npos) {
      position_of_fn = j;

      if (i == 0)
        d = LOG_F(s, j);
      else if (i == 1)
        d = LN_F(s, j);
      else if (i == 2)
        d = SIN_F(s, j);
      else if (i == 3)
        d = COS_F(s, j);
      else if (i == 4)
        d = TAN_F(s, j);
      else if (i == 5)
        d = INT_F(s, j);
      else if (i == 6)
        d = ABS_F(s, j);
      else if (i == 7)
        d = SGN_F(s, j);
      else if (i == 8)
        d = SQR_F(s, j);
      else if (i == 9) {
        s = FAC_F(s, j);
        return; // no break
      } else if (i == 10)
        d = B2D_F(s, j);
      else if (i == 11)
        d = H2D_F(s, j);
      else if (i == 12)
        d = SOLVE_F(s, j);

      converter::to_string(d, str_cnv);
      s.replace(position_of_fn, j - position_of_fn + 1, str_cnv);
    } else
      ++i;
  } // while

  // now there must be no letter left in s
  // only digits and/or ^ * / % + - . ! : ; can be accepted
  static const string legal_symbols = "^*/%+-.e!:;x";
  for (size_t i = 0; i < s.length(); ++i) {
    if (IS_HEX(s[i]) == false && legal_symbols.find(s[i]) == string::npos)
      throw parameter_exception(UNDEFINED_CHARACTERS);

  } // for i
} // fun_parse

void math_parser::fac_parse(string &s) {
  size_t j = 0, i = 0;
  double a = 0;
  string str_cnv;

  while (i < s.length()) {
    switch (s[i]) {
      case '!': {
        j = i - 1;
        a = read_previous_number(s, j);

        if (a < 0 || floor(a) != a)
          throw operation_not_allowed_exception(INVALID_OPERATION);

        if (a > MAX_FACT)
          throw parameter_exception(OUT_OF_RANGE);

        converter::to_string(fact((unsigned int) a), str_cnv);
        s.replace(j, i - j + 1, str_cnv);
        i = j + str_cnv.length();
        break;
      } // case '!'

      default: {
        ++i;
      } // default
    } // switch
  } // while
} // fac_parse

void math_parser::pow_parse(string &s) {
  size_t j = 0, i = 0;
  double a = 0.0, b = 0.0;
  double hlp1 = 0.0;
  string str_cnv;

  while (i < s.length()) {
    switch (s[i]) {
      case '^': {
        j = i - 1;
        a = read_previous_number(s, j);
        ++i;
        b = read_next_number(s, i);

        if (a < 0) {
          if ((b > -1 && b < 1 && b != 0) || floor(b) != b)
            throw operation_not_allowed_exception(INVALID_OPERATION);
        } // if

        hlp1 = pow(a, b);
        if (ABS(hlp1) > MAX_NUMBER)
          throw parameter_exception(OUT_OF_RANGE);

        converter::to_string(hlp1, str_cnv);
        s.replace(j, i - j + 1, str_cnv);
        i = j + str_cnv.length();
        break;
      } // case '^'

      default: {
        ++i;
      } // default
    } // switch
  } // while
} // pow_parse

void math_parser::mul_parse(string &s) {
  size_t j = 0, i = 0;
  double a = 0.0, b = 0.0;
  double hlp1 = 0.0;
  string str_cnv;

  while (i < s.length()) {
    switch (s[i]) {
      case '*': {
        j = i - 1;
        a = read_previous_number(s, j);
        ++i;
        b = read_next_number(s, i);

        hlp1 = a * b;
        if (ABS(hlp1) > MAX_NUMBER)
          throw parameter_exception(OUT_OF_RANGE);

        converter::to_string(hlp1, str_cnv);
        s.replace(j, i - j + 1, str_cnv);
        i = j + str_cnv.length();
        break;
      } // case '*'

      case '/': {
        j = i - 1;
        a = read_previous_number(s, j);
        ++i;
        b = read_next_number(s, i);

        if (b == 0)
          throw operation_not_allowed_exception(INVALID_OPERATION); // Division by zero is undefined

        hlp1 = a / b;
        if (ABS(hlp1) > MAX_NUMBER)
          throw parameter_exception(OUT_OF_RANGE);

        converter::to_string(hlp1, str_cnv);
        s.replace(j, i - j + 1, str_cnv);
        i = j + str_cnv.length();
        break;
      } // case '/'

      case '%': {
        j = i - 1;
        a = read_previous_number(s, j);
        ++i;
        b = read_next_number(s, i);

        if (ABS(a) > MAX_NUMBER || ABS(b) > MAX_NUMBER)
          throw parameter_exception(OUT_OF_RANGE);

        // calculates a % b (modulo)
        hlp1 = a - (b * (long long) (a / b));

        converter::to_string(hlp1, str_cnv);
        s.replace(j, i - j + 1, str_cnv);
        i = j + str_cnv.length();
        break;
      } // case '%'

      default: {
        ++i;
      } // default
    } // switch
  } // while
} // mul_parse

void math_parser::plu_parse(string &s) {
  size_t j = 0, i = 0;
  double a = 0.0, b = 0.0;
  double hlp1 = 0.0;
  string str_cnv;

  while (i < s.length()) {
    switch (s[i]) {
      case '+': {
        if ((i == 0) || (i > 0 && s[i - 1] == 'e')) {
          ++i;
          continue; // + at the beginning or scientific number and not plus operator (example 1.23e+123)
        } // if

        j = i - 1;
        a = read_previous_number(s, j);
        ++i;
        b = read_next_number(s, i);

        hlp1 = a + b;
        if (ABS(hlp1) > MAX_NUMBER)
          throw parameter_exception(OUT_OF_RANGE);

        converter::to_string(hlp1, str_cnv);
        s.replace(j, i - j + 1, str_cnv);
        i = j + str_cnv.length();
        break;
      } // case '+'

      case '-': {
        if (i > 0 && s[i - 1] == 'e') {
          ++i;
          continue; // scientific number and not plus operator (example 1.23e+123)
        } // if

        if (i == 0) {
          // minus sign, not minus operator

          if (s[1] == '-') {
            // when --x, ---x, ----x, ...
            s.erase(0, 2);
            i = 0;
          } else
            ++i;

          break;
        } // if

        j = i - 1;
        a = read_previous_number(s, j);
        ++i;
        b = read_next_number(s, i);

        hlp1 = a - b;
        if (ABS(hlp1) > MAX_NUMBER)
          throw parameter_exception(OUT_OF_RANGE);

        converter::to_string(hlp1, str_cnv);
        s.replace(j, i - j + 1, str_cnv);
        i = j + str_cnv.length();
        break;
      } // case '-'

      default : {
        ++i;
      } // default
    } // switch
  } // while
} // plu_parse

void math_parser::check_parenthesis(const string &s) {
  const size_t len = s.length();
  unsigned short left_par = 0;
  unsigned short right_par = 0;

  for (size_t i = 0; i < len; ++i) {
    if (s[i] == '(')
      ++left_par;
    else if (s[i] == ')')
      ++right_par;
  } // for

  if (left_par != right_par)
    throw parameter_exception(PARENTHESES_CHECK_FAILED);
} // check_parenthesis

void math_parser::check_mistakes(const string &s) {
  static const string mistakes[] = { "..", "---" }; // -- could be OK
  static const string operators = "+-*/%^!";
  const size_t len = s.length();

  if (len > MAX_INPUT_LENGTH)
    throw parameter_exception(INPUT_TOO_LONG);

  for (size_t i = 0; i < len; ++i) {
    if (operators.find(s[i]) != string::npos) {
      if ((i + 1) < len && s[i + 1] != '-' && operators.find(s[i + 1]) != string::npos)
        throw parameter_exception(INPUT_CHECK_FAILED); // two operators right after each other not allowed
    } // if
  } // for

  for (unsigned short i = 0; i < sizeof(mistakes) / sizeof(string) ; ++i) {
    if (s.find(mistakes[i]) != string::npos)
      throw parameter_exception(INPUT_CHECK_FAILED);
  } // for
} // check_mistakes

string &math_parser::parse(const string &s, string &output, bool separator) {
  output.resize(0);

  if (s.length() == 0)
    return output;

  output = s;

  str::remove_all(output, " "); // removes all spaces
  str::replace_all(output, "PI", PI_STR);
  str::replace_all(output, "E", E_STR);
  str::to_lower(output);

  check_parenthesis(output);
  check_mistakes(output);

  size_t begin = 0, end = 0;
  string s1 = par_parse(output, begin, end);

  while (s1.length() > 0) {
    fun_parse(s1);
    pow_parse(s1);
    fac_parse(s1);
    mul_parse(s1);
    plu_parse(s1);

    output.replace(begin, end - begin + 1, s1);

    s1 = par_parse(output, begin, end);
  } // while

  fun_parse(output);
  pow_parse(output);
  fac_parse(output);
  mul_parse(output);
  plu_parse(output);

  if (separator == true) {
    converter::thousand_separator(output, s1);
    output = s1;
  } // if

  return output;
} // parse
